﻿/*
 Me2api ActionScript 3.0 Wrapper by bluemetal(bluemetal@gmail.com, http://me2day.net/bluemetal
*/
package me2day.api
{
	import com.adobe.crypto.MD5;
	import com.adobe.serialization.json.JSON;
	import com.dynamicflash.util.Base64;
	
	import flash.events.*;
	import flash.net.*;
	import flash.utils.ByteArray;
	import flash.utils.Dictionary;
	import flash.xml.*;
	
	import me2day.api.data.*;
	import me2day.api.events.Me2dayErrorEvent;
	import me2day.api.events.Me2dayEvent;
		
	/**
	* Contains an array of Me2day user objects in the data object
	*/
	[Event(name="onFriendsResult", type="me2day.api.events.Me2dayEvent")]
	/**
	* Contains an Me2day user objects in the data object
	*/
	[Event(name="onPersonResult", type="me2day.api.events.Me2dayEvent")]
	/**
	* Contains a feedback of create_post in the data object
	*/
	[Event(name="onCreatePost", type="me2day.api.events.Me2dayEvent")]
	/**
	* Contains an Me2day post obejcts in the data object
	*/
	[Event(name="onLatestResult", type="me2day.api.events.Me2dayEvent")]
	/**
	* true or false in the data object
	*/
	[Event(name="onNoop", type="me2day.api.events.Me2dayEvent")]
	/**
	* Contains an array of Me2dayComment obejcts in the data object
	*/
	[Event(name="onCommentResult", type="me2day.api.events.Me2dayEvent")]
	/**
	* Contains a feedback of create_comment in the data object
	*/
	[Event(name="onCreateComment", type="me2day.api.events.Me2dayEvent")]
	
	[Event(name="onMetoosResult", type="me2day.api.events.Me2dayEvent")]
	
	[Event(name="onDoMetooResult", type="me2day.api.events.Me2dayEvent")]
	
	[Event(name="onSetDescription", type="me2day.api.events.Me2dayEvent")]
	
	[Event(name="onGetAuthUrlResult", type="me2day.api.events.Me2dayEvent")]
	
	[Event(name="onGetTrackCommentsResult", type="me2day.api.events.Me2dayEvent")]
	
	public class Me2day extends EventDispatcher
	{
		public static const ME2DAY_URL:String = "http://me2day.net/";
		
		private var userid:String;
		private var userkey:String;
		private var applicationKey:String = "77c477421872469da62882ee15a0fbad";	// me2DC 어플리케이션 키. 라이브러리 외부에서 셋팅하도록 수정해야 함.
		
		private var friendsLoader:URLLoader = new URLLoader;
		private var personLoader:URLLoader = new URLLoader;
		private var createPostLoader:URLLoader = new URLLoader;
		private var latestLoader:URLLoader = new URLLoader;
		private var noopLoader:URLLoader = new URLLoader;
		private var commentsLoader:URLLoader = new URLLoader;
		private var createCommentLoader:URLLoader = new URLLoader;
		private var metoosLoader:URLLoader = new URLLoader;
		private var doMetooLoader:URLLoader = new URLLoader;
		private var setDescriptionLoader:URLLoader = new URLLoader;
		private var getAuthUrlLoader:URLLoader = new URLLoader;
		private var getFullAuthTokenLoader:URLLoader = new URLLoader;
		private var trackCommentsLoader:URLLoader = new URLLoader;
		
		private var errortypeDic:Dictionary = new Dictionary;
		public function Me2day()
		{
			var loaders:Array = [
			{loader:friendsLoader,		eventtype:Event.COMPLETE,				handler:friendsHandler,		errortype:Me2dayErrorEvent.SERVICE_TYPE_FRIENDSLOADER},
			{loader:personLoader,		eventtype:Event.COMPLETE,				handler:personHandler,		errortype:Me2dayErrorEvent.SERVICE_TYPE_PERSONLOADER},
			{loader:createPostLoader,	eventtype:Event.COMPLETE,				handler:createPostHandler,	errortype:Me2dayErrorEvent.SERVICE_TYPE_CREATEPOSTLOADER},
			{loader:latestLoader,		eventtype:Event.COMPLETE,				handler:latestHandler,		errortype:Me2dayErrorEvent.SERVICE_TYPE_LATESTLOADER},
			{loader:noopLoader,			eventtype:HTTPStatusEvent.HTTP_STATUS,	handler:noopHandler,		errortype:Me2dayErrorEvent.SERVICE_TYPE_NOOPLOADER},
			{loader:commentsLoader,		eventtype:Event.COMPLETE,				handler:commentsHandler,	errortype:Me2dayErrorEvent.SERVICE_TYPE_COMMENTSLOADER},
			{loader:createCommentLoader,eventtype:HTTPStatusEvent.HTTP_STATUS,	handler:createCommentHandler,errortype:Me2dayErrorEvent.SERVICE_TYPE_CREATECOMMENTLOADER},
			{loader:setDescriptionLoader,eventtype:HTTPStatusEvent.HTTP_STATUS,	handler:setDescriptionHandler,errortype:Me2dayErrorEvent.SERVICE_TYPE_SETDESCRIPTIONLOADER},
			{loader:metoosLoader,		eventtype:Event.COMPLETE,				handler:metoosHandler,		errortype:Me2dayErrorEvent.SERVICE_TYPE_METOOSLOADER},
			{loader:doMetooLoader,		eventtype:Event.COMPLETE,				handler:doMetooHandler,		errortype:Me2dayErrorEvent.SERVICE_TYPE_DOMETOOLOADER},
			{loader:getAuthUrlLoader,	eventtype:Event.COMPLETE,				handler:getAuthUrlHandler,	errortype:Me2dayErrorEvent.SERVICE_TYPE_GETAUTHURL},
			{loader:getFullAuthTokenLoader,	eventtype:Event.COMPLETE,			handler:getFullAuthTokenHandler,	errortype:Me2dayErrorEvent.SERVICE_TYPE_GETFULLAUTHTOKEN},
			{loader:trackCommentsLoader,eventtype:Event.COMPLETE,				handler:getTrackCommentsHandler,	errortype:Me2dayErrorEvent.SERVICE_TYPE_GETTRACKCOMMENTS}
			];
			
			for each ( var item:Object in loaders )
			{
				item["loader"].addEventListener(item["eventtype"], item["handler"]);
				item["loader"].addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
				item["loader"].addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
				errortypeDic[item["loader"]] = item["errortype"];
			}
			
		}
		
		private function me2dayRequest(URL:String, isAuth:Boolean = false):URLRequest
		{
			var req:URLRequest = new URLRequest(URL);
			req.requestHeaders = [];
			
			if (isAuth)
			{
				var nonce:String = "AABBCCDD";
				var pass:String;
				if ( fullAuthToken )
					pass = "full_auth_token " + fullAuthToken;
				else
					pass = nonce + MD5.hash(nonce + userkey);
				
				var credentials:String = Base64.encode(userid + ":" + pass);
				/* 
				var encoder:Base64Encoder = new Base64Encoder();
				encoder.encode(userid + ":" + pass);				
				var credentials:String = encoder.flush();  */
				
				var authHeader:URLRequestHeader = new URLRequestHeader("Authorization", "Basic " + credentials);
				req.requestHeaders.push(authHeader);
				
				//req.shouldAuthenticate = false;				
			}
			
			var appHeader:URLRequestHeader = new URLRequestHeader("me2_application_key", applicationKey);
			req.requestHeaders.push(appHeader);
				
			return req;
		}
		
		public function setApplicationKey(key:String):void
		{
			applicationKey = key;	
		}
		
		private var fullAuthToken:String;
		public function setFullAuthToken(token:String):void
		{
			fullAuthToken = token;
		}
		
		/**
		*  
		*/
		public function getAuthUrl():void
		{	
			getAuthUrlLoader.load(me2dayRequest(ME2DAY_URL + "api/get_auth_url"));
		}
		
		private var authToken:String;
		private function getAuthUrlHandler(e:Event):void
		{
			var xml:XML = new XML(getAuthUrlLoader.data);
			var me2dayAuthURL :Me2dayAuthURL = new Me2dayAuthURL(xml);
			
			var r:Me2dayEvent = new Me2dayEvent(Me2dayEvent.ON_GET_AUTH_URL_RESULT);
			r.data["url"] = me2dayAuthURL.url;
			r.data["token"] = me2dayAuthURL.token;
			authToken = me2dayAuthURL.token;
			dispatchEvent(r);
		}
		
		public function getFullAuthToken():void
		{	
			getFullAuthTokenLoader.load(me2dayRequest(ME2DAY_URL + "api/get_full_auth_token?" + "token=" + authToken));
		}
		
		private function getFullAuthTokenHandler(e:Event):void
		{
			var xml:XML = new XML(getFullAuthTokenLoader.data);
			var me2dayGetFullToken :Me2dayGetFullToken = new Me2dayGetFullToken(xml);	
			var r:Me2dayEvent = new Me2dayEvent(Me2dayEvent.ON_GET_FULL_AUTH_TOKEN_RESULT);
			r.data["user_id"] = me2dayGetFullToken.user_id;
			r.data["full_auth_token"] = me2dayGetFullToken.full_auth_token;
			userid = me2dayGetFullToken.user_id;
			fullAuthToken = me2dayGetFullToken.full_auth_token;
			dispatchEvent(r);
		}
		
		/**
		*  setAuth should be called before any methods that require authentication.
		*/
		public function setAuth(id:String, key:String):void
		{	
			userid = id;
			userkey = key;
		}
		
		/**
		* Check for authentication or test. Authentication required.
		*/
		public function noop():void
		{
			noopLoader.load(me2dayRequest(ME2DAY_URL + "api/noop.xml", true));			
		}
		
		/**
		* Loads a list of Me2day friends. Authentication not required.
		*/
		public function loadFriends(id:String, type:String):void
		{
			var request:URLRequest = me2dayRequest(ME2DAY_URL + "api/get_friends/" + id + ".xml");
			var vars:URLVariables = new URLVariables();
			vars["scope"] = type; 
			request.data = vars;
			friendsLoader.load(request);
		}
		
		/**
		* Load a person Infomation. Authentication not required.
		*/
		public function loadPerson(id:String):void
		{
			personLoader.load(me2dayRequest(ME2DAY_URL + "api/get_person/" + id + ".xml"));
		}
		
		/**
		* Load a latest post. Authentication not required.
		*/
		public function loadLatest(id:String):void
		{
			latestLoader.load(me2dayRequest(ME2DAY_URL + "api/get_latests/" + id + ".xml"));
		}
		
		/**
		* Load comments of post. Authentication not required.
		*/
		public function loadComments(post_id:String):void
		{
			var request:URLRequest = me2dayRequest(ME2DAY_URL + "api/get_comments.xml");
			var vars:URLVariables = new URLVariables();
			vars["post_id"] = post_id; 
			request.data = vars;
			commentsLoader.load(request);
		}
		
		public function loadMetoos(post_id:String):void
		{
			var request:URLRequest = me2dayRequest(ME2DAY_URL + "api/get_metoos.xml");
			var vars:URLVariables = new URLVariables();
			vars["post_id"] = post_id; 
			request.data = vars;
			metoosLoader.load(request);
		}
		
		public function doMetoo(post_id:String):void
		{
			var request:URLRequest = me2dayRequest(ME2DAY_URL + "api/metoo.xml", true);
			var vars:URLVariables = new URLVariables();
			vars["post_id"] = post_id; 
			request.data = vars;
			doMetooLoader.load(request);
		}
		
		private const BOUNDARY :String = "---------------------------7d76d1b56035e";

		private function getMultipartData(body:String, tags:String, icon:String, attachment:ByteArray, sms:Boolean):ByteArray
		{
			var buffer:ByteArray = new ByteArray;
			buffer.writeUTFBytes( '--'+BOUNDARY+'\r\n');
			buffer.writeUTFBytes( 'content-disposition: form-data; name="attachment"; filename="me2photo.jpg"\r\n');
			buffer.writeUTFBytes( 'Content-Transfer-Encoding: binary\r\n');
			buffer.writeUTFBytes( 'Content-Length: '+attachment.length+'\r\n');
			buffer.writeUTFBytes( 'Content-Type: application/octet-stream\r\n');
			buffer.writeUTFBytes( '\r\n');			
			buffer.writeBytes(attachment, 0, attachment.length);
			
			buffer.writeUTFBytes( '\r\n--'+BOUNDARY+'\r\n');
			buffer.writeUTFBytes( 'Content-Disposition: form-data; name="post[body]"\r\n');
			buffer.writeUTFBytes( '\r\n');		
			buffer.writeUTFBytes( body );
			buffer.writeUTFBytes( '\r\n--'+BOUNDARY+'\r\n');
			buffer.writeUTFBytes( 'Content-Disposition: form-data; name="post[tags]"\r\n');
			buffer.writeUTFBytes( '\r\n');		
			buffer.writeUTFBytes( tags );
			buffer.writeUTFBytes( '\r\n--'+BOUNDARY+'\r\n');
			buffer.writeUTFBytes( 'Content-Disposition: form-data; name="post[icon]"\r\n');
			buffer.writeUTFBytes( '\r\n');		
			buffer.writeUTFBytes( icon );
			buffer.writeUTFBytes( '\r\n--'+BOUNDARY+'\r\n');
			buffer.writeUTFBytes( 'Content-Disposition: form-data; name="receive_sms"\r\n');
			buffer.writeUTFBytes( '\r\n');		
			buffer.writeUTFBytes( sms.toString() );
			buffer.writeUTFBytes( '\r\n--'+BOUNDARY+'--\r\n');
			
			
			return buffer;
		}
		
		/**
		 * create a post. Authentication required.
		*/
		public function createPost(body:String, tags:String, icon:String, attachment:ByteArray, sms:Boolean,
									longitude:Number, latitude:Number):void
		{
			//if (body.length <= 150)
			{
				var request:URLRequest = me2dayRequest(ME2DAY_URL + "api/create_post/" + userid + ".xml", true);
				request.method = "POST"
				
            	if ( attachment )
            	{
            		request.contentType = "multipart/form-data; boundary=" + BOUNDARY;
            		request.data = getMultipartData(body, tags, icon, attachment, sms);
            	}
            	else
            	{
            		var vars:URLVariables = new URLVariables();
				
	            	vars["post[body]"] = body;
	            	vars["post[tags]"] = tags;
	            	vars["post[icon]"] = icon;
	            	vars["receive_sms"] = sms.toString();
	            	if ( longitude > 0 )
	            		vars["longitude"] = longitude;
	            	if ( latitude > 0 )
	            	vars["latitude"] = latitude;
	            	request.data = vars;
	            }
	            
				try
				{
					createPostLoader.load(request);				
				} 
				catch (error:Error)
				{
					var r:Me2dayErrorEvent = new Me2dayErrorEvent(Me2dayErrorEvent.SERVICE_TYPE_CREATEPOSTLOADER);			
					r.message = "Error가 발생하였습니다.\n" + error.message;
					dispatchEvent(r);
					trace ("Unable to create post");
				}
			}
			/* else
			{
				trace("limited to 150 characters");
			}	 */
		}
		
		/**
		 * create a comment. Authentication required.
		*/
		public function createComment(post_id:String, body:String):void
		{
			//if (body.length <= 150)
			{
				var request:URLRequest = me2dayRequest(ME2DAY_URL + "api/create_comment.xml", true);
				request.method = "POST"
				var vars:URLVariables = new URLVariables();
				
            	vars["post_id"] = post_id;
            	vars["body"] = body;
            	
				request.data = vars;
				
				try
				{
					createCommentLoader.load(request);
				} 
				catch (error:Error)
				{
					var r:Me2dayErrorEvent = new Me2dayErrorEvent(Me2dayErrorEvent.SERVICE_TYPE_CREATECOMMENTLOADER);			
					r.message = "Error가 발생하였습니다.\n" + error.message;
					dispatchEvent(r);
					trace ("Unable to create comment");
				}
			}
			/* else
			{
				trace("limited to 150 characters");
			}	 */
		}
		
		public function setDescription(description :String):void
		{
			var request:URLRequest = me2dayRequest(ME2DAY_URL + "api/set_description/" + userid + ".xml", true);
			request.method = "POST";
			
    		var vars:URLVariables = new URLVariables();
		
        	vars["description"] = description;
        	request.data = vars;
        	
			try
			{
				setDescriptionLoader.load(request);				
			} 
			catch (error:Error)
			{
				var r:Me2dayErrorEvent = new Me2dayErrorEvent(Me2dayErrorEvent.SERVICE_TYPE_SETDESCRIPTIONLOADER);			
				r.message = "Error가 발생하였습니다.\n" + error.message;
				dispatchEvent(r);
				trace ("Unable to set_description");
			}
		}
		
		private function friendsHandler(e:Event):void 
		{
			var xml:XML = new XML(friendsLoader.data);
			var arr:Array = new Array();
            for each (var tempXML:XML in xml.children()) 
			{
				var me2dayUser:Me2dayUser = new Me2dayUser(tempXML);
                arr.push(me2dayUser);
            }
			var r:Me2dayEvent = new Me2dayEvent(Me2dayEvent.ON_FRIENDS_RESULT);
			r.data = arr;
			dispatchEvent(r);
		}
		
		private function personHandler(e:Event):void
		{
			var xml:XML = new XML(personLoader.data);
			var me2dayUser:Me2dayUser = new Me2dayUser(xml);
			var r:Me2dayEvent = new Me2dayEvent(Me2dayEvent.ON_PERSON_RESULT);
			r.data = me2dayUser;
			dispatchEvent(r);
		}
		
		private function latestHandler(e:Event):void
		{
			var xml:XML = new XML(latestLoader.data);
			var arr:Array = new Array();
			var r:Me2dayEvent = new Me2dayEvent(Me2dayEvent.ON_LATEST_RESULT);
			for each (var tempXML:XML in xml.children()) 
			{
				var me2dayPost:Me2dayPost = new Me2dayPost(tempXML);
                arr.push(me2dayPost);
            }
            r.data = arr;
			dispatchEvent(r);
		}
		
		private function createPostHandler(e:Event):void
		{
			var xml:XML = new XML(createPostLoader.data);			
			var me2dayPost:Me2dayPost = new Me2dayPost(xml);
			            
			var r:Me2dayEvent = new Me2dayEvent(Me2dayEvent.ON_CREATE_POST);
			r.data.post = me2dayPost;
			dispatchEvent(r);
		}
		
		private function noopHandler(e:HTTPStatusEvent):void
		{
			var r:Me2dayEvent = new Me2dayEvent(Me2dayEvent.ON_NOOP);
			
			if (e.status == 200)
    		{
    			r.data = true;
    		}
    		else
    		{
    			r.data = false;
    		}
    		
			dispatchEvent(r);
		}
		
		private function commentsHandler(e:Event):void
		{
			var xml:XML = new XML(commentsLoader.data);
			var arr:Array = new Array();
            for each (var tempXML:XML in xml.children()) 
			{
				var me2dayComment:Me2dayComment = new Me2dayComment(tempXML);
                arr.push(me2dayComment);
            } 
			var r:Me2dayEvent = new Me2dayEvent(Me2dayEvent.ON_COMMENT_RESULT);
			r.data.post_id = xml.attribute("post_id").toString();
			r.data.comments = arr;
			dispatchEvent(r);
		}
		
		private function metoosHandler(e:Event):void
		{
			var xml:XML = new XML(metoosLoader.data);
			var arr:Array = new Array();
            for each (var tempXML:XML in xml.children()) 
			{
				var me2dayMetoo:Me2dayMetoo = new Me2dayMetoo(tempXML);
                arr.push(me2dayMetoo);
            } 
			var r:Me2dayEvent = new Me2dayEvent(Me2dayEvent.ON_METOOS_RESULT);
			r.data.post_id = xml.attribute("post_id").toString();
			r.data.metoos = arr;
			dispatchEvent(r);
		}
		
		private function doMetooHandler(e:Event):void
		{
			var xml:XML = new XML(doMetooLoader.data);
			
			var r:Me2dayEvent = new Me2dayEvent(Me2dayEvent.ON_DOMETOO_RESULT);
			var error:Object = xml.children()[0];
			r.data.message = error["message"];
			r.data.description = error["description"];
			dispatchEvent(r);
		}
		
		private function createCommentHandler(e:HTTPStatusEvent):void
		{
			var r:Me2dayEvent = new Me2dayEvent(Me2dayEvent.ON_CREATE_COMMENT);
			r.data = e.status;
			dispatchEvent(r);
		}
		
		private function setDescriptionHandler(e:HTTPStatusEvent):void
		{
			var r:Me2dayEvent = new Me2dayEvent(Me2dayEvent.ON_SET_DESCRIPTION);
			r.data = e.status;
			dispatchEvent(r);
		}
		
		public function getTrackComments(scope:String = "", count:int = 0, after:String = ""):void
		{
			var request:URLRequest = me2dayRequest(ME2DAY_URL + "api/track_comments/" + userid + ".json");
			var vars:URLVariables = new URLVariables();
			if ( scope )
				vars["scope"] = scope; 
			if ( count > 0 )
				vars["count"] = count; 
			if ( after )
				vars["after"] = after; 
			request.data = vars;
			trackCommentsLoader.load(request);
		}
		
		private function getTrackCommentsHandler(e:Event):void
		{
			var jsonObj:Object = JSON.decode( trackCommentsLoader.data );
			
			var commentLogs:Me2dayCommentLogs = new Me2dayCommentLogs(jsonObj);
			
			var r:Me2dayEvent = new Me2dayEvent(Me2dayEvent.ON_GET_TRACK_COMMENTS_RESULT);
			r.data.me = jsonObj["me"];
			r.data.commentLogs = commentLogs;
			dispatchEvent(r);
		}
		
		private function errorHandler(event:IOErrorEvent):void
		{
			trace("event.currentTarget:"+event.currentTarget);
			trace("event.eventPhase:"+event.eventPhase);
			trace("event.target:"+event.target);
			trace("event.text:"+event.text);			
			
			var r:Me2dayErrorEvent = new Me2dayErrorEvent(errortypeDic[event.currentTarget]);			
			r.message = "IO Error가 발생하였습니다.\n" + event.text;
			dispatchEvent(r);
		}
		
		private function securityErrorHandler(event:SecurityError):void
		{
			trace("event.name:"+event.name);
			trace("event.message:"+event.message);			
			
			var r:Me2dayErrorEvent = new Me2dayErrorEvent(Me2dayErrorEvent.SECURITY_ERROR);
			r.message = "보안 에러가 발생하였습니다.\n[" + event.errorID + "]" + event.message;
			dispatchEvent(r);
		}
	}
}